package org.gbif.metrics.cube.occurrence;

import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.urbanairship.datacube.DataCube;
import com.urbanairship.datacube.Dimension;
import com.urbanairship.datacube.ReadBuilder;
import com.urbanairship.datacube.Rollup;
import com.urbanairship.datacube.bucketers.BigEndianIntBucketer;
import com.urbanairship.datacube.bucketers.BooleanBucketer;
import com.urbanairship.datacube.bucketers.StringToBytesBucketer;
import com.urbanairship.datacube.ops.LongOp;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * The cube definition for occurrence records.
 */
public class OccurrenceCube {

  private static Logger LOG = LoggerFactory.getLogger(OccurrenceCube.class);

  public static final Dimension<Boolean> IS_GEOREFERENCED = new Dimension<Boolean>("georeferenced", new BooleanBucketer(), true, 1, true);
  // TODO: when http://dev.gbif.org/issues/browse/OCC-77 is done
// public static final Dimension<BasisOfRecord> BASIS_OF_RECORD = new Dimension<BasisOfRecord>("basisOfRecord",
// new EnumToOrdinalBucketer<BasisOfRecord>(1), false, 1, true);
  public static final Dimension<String> BASIS_OF_RECORD = new Dimension<String>("basisOfRecord", new StringToBytesBucketer(), true, 7, true);
  public static final Dimension<String> KINGDOM = new Dimension<String>("kingdom", new StringToBytesBucketer(), true, 7, true);
  public static final Dimension<String> COUNTRY_ISO = new Dimension<String>("countryISO", new StringToBytesBucketer(), false, 2, true);
  public static final Dimension<String> HOST_COUNTRY_ISO = new Dimension<String>("hostCountryISO", new StringToBytesBucketer(), false, 2, true);
  public static final Dimension<String> DATASET_KEY = new Dimension<String>("datasetKey", new StringToBytesBucketer(), false, 36, true);
  public static final Dimension<Integer> YEAR = new Dimension<Integer>("year", new BigEndianIntBucketer(), false, 4, true);
  public static final Dimension<Integer> MONTH = new Dimension<Integer>("month", new BigEndianIntBucketer(), false, 4, true);
  public static final Dimension<Integer> TAXON_ID = new Dimension<Integer>("taxonID", new BigEndianIntBucketer(), false, 4, true);

  // Index to enable type casting at runtime (e.g. enables mapping from a string in the writebuilder)
  public static final Map<Dimension<?>, Class<?>> DIMENSION_TYPES = ImmutableMap.<Dimension<?>, Class<?>>builder()
    .put(IS_GEOREFERENCED, Boolean.class).put(BASIS_OF_RECORD, String.class).put(KINGDOM, String.class).put(COUNTRY_ISO, String.class)
    .put(HOST_COUNTRY_ISO, String.class).put(DATASET_KEY, String.class).put(YEAR, Integer.class).put(MONTH, Integer.class)
    .put(TAXON_ID, Integer.class).build();

  // Singleton instance if accessed through the instance() method
  public static final DataCube<LongOp> INSTANCE = newInstance();

  // Not for instantiation
  private OccurrenceCube() {
  }

  /**
   * A utility to construct a ReadBuilder from String based KVPs which handles type conversion. The keys must map to
   * the dimension names within the cube, and the values must be castable to the appropriate type.
   */
  public static ReadBuilder interpretedReadBuilder(Map<String, String> kvp) throws NumberFormatException {
    ReadBuilder b = new ReadBuilder(OccurrenceCube.INSTANCE);
    for (Entry<String, String> param : kvp.entrySet()) {
      // only read the first value
      String k = param.getKey();
      String v = param.getValue();

      // lookup any matching dimensions based on the name, and add them to the query builder if found
      for (Entry<Dimension<?>, Class<?>> typedDim : OccurrenceCube.DIMENSION_TYPES.entrySet()) {
        if (StringUtils.equalsIgnoreCase(k, typedDim.getKey().getName())) {
          Class<?> type = typedDim.getValue();

          if (Boolean.class == type) {
            b.at(typedDim.getKey(), Boolean.valueOf(v));
          } else if (Integer.class == type) {
            b.at(typedDim.getKey(), Integer.parseInt(v));
          } else if (String.class == type) {
            b.at(typedDim.getKey(), v);
          }
        }
      }
    }

    return b;
  }

  private static DataCube<LongOp> newInstance() {
    List<Dimension<?>> dimensions =
      ImmutableList.<Dimension<?>>of(IS_GEOREFERENCED, BASIS_OF_RECORD, KINGDOM, COUNTRY_ISO, HOST_COUNTRY_ISO, DATASET_KEY, YEAR, MONTH, TAXON_ID);
    List<Rollup> rollups =
      ImmutableList.of(new Rollup(BASIS_OF_RECORD), new Rollup(IS_GEOREFERENCED), new Rollup(KINGDOM), new Rollup(COUNTRY_ISO), new Rollup(
        HOST_COUNTRY_ISO), new Rollup(DATASET_KEY), new Rollup(YEAR), new Rollup(TAXON_ID), new Rollup(KINGDOM, BASIS_OF_RECORD), new Rollup(KINGDOM,
        IS_GEOREFERENCED), new Rollup(KINGDOM, BASIS_OF_RECORD, IS_GEOREFERENCED), new Rollup(TAXON_ID, YEAR), new Rollup(TAXON_ID, MONTH),
        new Rollup(DATASET_KEY, YEAR), new Rollup(DATASET_KEY, KINGDOM, BASIS_OF_RECORD, IS_GEOREFERENCED), new Rollup(COUNTRY_ISO, HOST_COUNTRY_ISO,
          KINGDOM, BASIS_OF_RECORD));

    return new DataCube<LongOp>(dimensions, rollups);
  }
}
